﻿using System;
using System.Collections.Generic;
using System.Web;
using System.IO;
using System.ComponentModel;

// SMTP send
using System.Net;
using System.Net.Mail;

// POP recieve
using OpenPop.Pop3;
using OpenPop.Mime;

using log4net;

namespace kindly
{
    public class Mail
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(Global));

        public static void Get(string attachmentPath, bool runAsync)
        {
            if (runAsync)
            {
                // run this as a background worker to make sure we return response fast
                // we're calling this from google app engine and need to finish inside 30 seconds
                BackgroundWorker getWorker = new BackgroundWorker();
                getWorker.DoWork += new DoWorkEventHandler(getWorker_DoWork);
                log.Info("Starting POP retrieval");
                getWorker.RunWorkerAsync(attachmentPath);
            }
            else
            {
                getMail(attachmentPath);
            }
        }

        public static void Send(string filePath, string[] recipients, bool runAsync)
        {
            SendInfo info = new SendInfo();
            info.recipients = recipients;
            info.filePath = filePath;

            if (runAsync)
            {
                // run this as a background worker to make sure we return response fast
                // we're calling this from google app engine and need to finish inside 30 seconds
                BackgroundWorker sendWorker = new BackgroundWorker();
                sendWorker.DoWork += new DoWorkEventHandler(sendWorker_DoWork);
                log.Info("Starting SMTP send");
                sendWorker.RunWorkerAsync(info);
            }
            else
            {
                sendMail(info);
            }
        }

        private struct SendInfo
        {
            public string[] recipients;
            public string filePath;
            public string smtpAccount;
        }

        private static string replacePlacehodlers(string template, SendInfo info)
        {
            Dictionary<string, string> placeholders = new Dictionary<string, string>();
            placeholders.Add("{fileName}", Path.GetFileName(info.filePath));
            placeholders.Add("{fileNameWithoutExtension}", Path.GetFileNameWithoutExtension(info.filePath));

            foreach (string placeholder in placeholders.Keys)
            {
                template = template.Replace(placeholder, placeholders[placeholder]);
            }

            return template;
        }

        private static bool sendMail(SendInfo info)
        {
            string currentSmtpAccount = Global.CurrentSmtpAccount;
            bool cycleStarted = false;

            foreach (string smtpAccount in IterTools.Cycle<string>(Global.SmtpAccounts))
            {
                if (smtpAccount.Equals(currentSmtpAccount) && cycleStarted)
                {
                    // we've done the full circle, all accounts have exceeded daily quota, throw error
                    throw new Exception("All accounts exceeded daily quota!");
                }
                else if (!smtpAccount.Equals(currentSmtpAccount) && !cycleStarted)
                {
                    continue; // find starting point
                }

                // we've started the cycle
                cycleStarted = true;

                // try send mail with current account
                info.smtpAccount = smtpAccount;
                try
                {
                    log.InfoFormat("Trying to send mail using account {0}", info.smtpAccount);
                    sendMailAccount(info);
                    Global.CurrentSmtpAccount = smtpAccount; // set this as a valid start point for next send
                    break;
                }
                catch (Exception ex)
                {
                    log.Error("Error while sending mail", ex);
                    continue;                   
                }
            }
            return true;
        }

        private static void sendMailAccount(SendInfo info)
        {
            Properties.Settings settings = kindly.Properties.Settings.Default;
            string fileName = Path.GetFileName(info.filePath);

            SmtpClient smtp = new SmtpClient
            {
                Host = settings.smtp_hostname,
                Port = settings.smtp_port,
                EnableSsl = settings.smtp_ssl,
                DeliveryMethod = SmtpDeliveryMethod.Network,
                UseDefaultCredentials = false,
                Credentials = new NetworkCredential(info.smtpAccount, settings.smtp_password)
            };

            using (MailMessage message = new MailMessage())
            {
                // main
                message.From = new MailAddress(info.smtpAccount, settings.smtp_fromDisplay);
                message.To.Add(new MailAddress("recipients@kindly.cz"));
                message.Subject = replacePlacehodlers(settings.smtp_subject, info);
                message.Body = replacePlacehodlers(settings.smtp_body, info);

                // recipients
                log.DebugFormat("Email has {0} recipients", info.recipients.Length);
                foreach (string recipient in info.recipients)
                {
                    message.Bcc.Add(recipient);
                }

                // attach
                log.DebugFormat("Attaching file {0}", Path.GetFileName(info.filePath));
                Attachment attachment = new Attachment(info.filePath);
                message.Attachments.Add(attachment);

                // send
                log.DebugFormat("Sending email message through {0}:{1}", smtp.Host, smtp.Port);
                smtp.Send(message);
            }
        }

        private static bool getMail(string attachmentPath)
        {
            Properties.Settings settings = kindly.Properties.Settings.Default;

            try
            {
                // The client disconnects from the server when being disposed
                using (Pop3Client client = new Pop3Client())
                {
                    // Connect to the server
                    log.DebugFormat("Connecting to POP server {0}:{1}", settings.pop_hostname, settings.pop_port);
                    client.Connect(settings.pop_hostname, settings.pop_port, settings.pop_ssl);

                    // Authenticate ourselves towards the server
                    client.Authenticate(settings.pop_username, settings.smtp_password);

                    // Get the number of messages in the inbox
                    int messageCount = client.GetMessageCount();
                    log.DebugFormat("{0} messages available ", messageCount);


                    // Messages are numbered in the interval: [1, messageCount]
                    // Ergo: message numbers are 1-based.
                    for (int i = 1; i <= messageCount; i++)
                    {
                        try
                        {
                            Message message = client.GetMessage(i);
                            if (message.Headers.From.Address.ToLower() != settings.pop_acceptFrom.ToLower())
                                continue;

                            List<MessagePart> parts = message.FindAllAttachments();

                            if (parts == null || parts.Count < 1)
                            {
                                log.DebugFormat("Message from {0} contains no attachments", message.Headers.From.Address);
                                continue;
                            }

                            foreach (MessagePart part in parts)
                            {
                                if (!part.IsAttachment) continue;

                                string fileName = part.FileName;
                                fileName = fileName.Replace(settings.pop_calibreSuffix, ""); // remove suffix

                                // save file
                                log.DebugFormat("Saving file {0} from {1}", fileName, message.Headers.From.Address);
                                FileInfo file = new FileInfo(Files.GetFilePath(fileName));
                                if (file.Exists) file.Delete();
                                part.SaveToFile(file);
                            }
                        }
                        catch (Exception ex)
                        {
                            log.Error("Failed to process message", ex);
                        }
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                log.Error("Error recieving mail", ex);
                throw;
            }
        }


        static void sendWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            SendInfo info = (SendInfo)e.Argument;
            sendMail(info);
        }
 
        static void getWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            string attachmentPath = e.Argument.ToString();
            getMail(attachmentPath);
        }

    }
}